/*-------------------------------------------------------------------------
 *
 * ginxlog.c
 *	  倒排索引的 WAL 重放逻辑。
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *			 src/backend/access/gin/ginxlog.c
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include "access/bufmask.h"
#include "access/gin_private.h"
#include "access/ginxlog.h"
#include "access/xlogutils.h"
#include "utils/memutils.h"

static MemoryContext opCtx;		/* 工作内存用于操作 */

static void fc_ginRedoClearIncompleteSplit(XLogReaderState *fc_record, uint8 fc_block_id)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	Buffer		fc_buffer;
	Page		fc_page;

	if (XLogReadBufferForRedo(fc_record, fc_block_id, &fc_buffer) == BLK_NEEDS_REDO)
	{
		fc_page = (Page) BufferGetPage(fc_buffer);
		GinPageGetOpaque(fc_page)->flags &= ~GIN_INCOMPLETE_SPLIT;

		PageSetLSN(fc_page, fc_lsn);
		MarkBufferDirty(fc_buffer);
	}
	if (BufferIsValid(fc_buffer))
		UnlockReleaseBuffer(fc_buffer);
}

static void fc_ginRedoCreatePTree(XLogReaderState *fc_record)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	ginxlogCreatePostingTree *fc_data = (ginxlogCreatePostingTree *) XLogRecGetData(fc_record);
	char	   *fc_ptr;
	Buffer		fc_buffer;
	Page		fc_page;

	fc_buffer = XLogInitBufferForRedo(fc_record, 0);
	fc_page = (Page) BufferGetPage(fc_buffer);

	GinInitBuffer(fc_buffer, GIN_DATA | GIN_LEAF | GIN_COMPRESSED);

	fc_ptr = XLogRecGetData(fc_record) + sizeof(ginxlogCreatePostingTree);

	/* 位置页面数据 */
	memcpy(GinDataLeafPageGetPostingList(fc_page), fc_ptr, fc_data->size);

	GinDataPageSetDataSize(fc_page, fc_data->size);

	PageSetLSN(fc_page, fc_lsn);

	MarkBufferDirty(fc_buffer);
	UnlockReleaseBuffer(fc_buffer);
}

static void fc_ginRedoInsertEntry(Buffer fc_buffer, bool fc_isLeaf, BlockNumber fc_rightblkno, void *fc_rdata)
{
	Page		fc_page = BufferGetPage(fc_buffer);
	ginxlogInsertEntry *fc_data = (ginxlogInsertEntry *) fc_rdata;
	OffsetNumber fc_offset = fc_data->offset;
	IndexTuple	fc_itup;

	if (fc_rightblkno != InvalidBlockNumber)
	{
		/* 在分裂后更新到右页面的链接 */
		Assert(!GinPageIsLeaf(fc_page));
		Assert(fc_offset >= FirstOffsetNumber && fc_offset <= PageGetMaxOffsetNumber(fc_page));
		fc_itup = (IndexTuple) PageGetItem(fc_page, PageGetItemId(fc_page, fc_offset));
		GinSetDownlink(fc_itup, fc_rightblkno);
	}

	if (fc_data->isDelete)
	{
		Assert(GinPageIsLeaf(fc_page));
		Assert(fc_offset >= FirstOffsetNumber && fc_offset <= PageGetMaxOffsetNumber(fc_page));
		PageIndexTupleDelete(fc_page, fc_offset);
	}

	fc_itup = &fc_data->tuple;

	if (PageAddItem(fc_page, (Item) fc_itup, IndexTupleSize(fc_itup), fc_offset, false, false) == InvalidOffsetNumber)
	{
		RelFileNode fc_node;
		ForkNumber	fc_forknum;
		BlockNumber fc_blknum;

		BufferGetTag(fc_buffer, &fc_node, &fc_forknum, &fc_blknum);
		elog(ERROR, "failed to add item to index page in %u/%u/%u",
			 fc_node.spcNode, fc_node.dbNode, fc_node.relNode);
	}
}

/*
 * 重新压缩发布列表。由于在一个地方进行所有更改并不总是可能的，
 * 因为这可能需要比我们在页面上拥有的更多的空间。
 * 因此，一旦需要修改，我们将页面未处理的尾部
 * 复制到单独分配的内存块中，以便进一步读取原始
 * 段的版本。由于这一点，我们不必担心在原地移动页面数据。
 */
static void fc_ginRedoRecompress(Page fc_page, ginxlogRecompressDataLeaf *fc_data)
{
	int			fc_actionno;
	int			fc_segno;
	GinPostingList *fc_oldseg;
	Pointer		fc_segmentend;
	char	   *fc_walbuf;
	int			fc_totalsize;
	Pointer		fc_tailCopy = NULL;
	Pointer		fc_writePtr;
	Pointer		fc_segptr;

	/*
	 * 如果页面是预9.4格式，首先转换为新格式。
	 */
	if (!GinPageIsCompressed(fc_page))
	{
		ItemPointer fc_uncompressed = (ItemPointer) GinDataPageGetData(fc_page);
		int			fc_nuncompressed = GinPageGetOpaque(fc_page)->maxoff;
		int			fc_npacked;

		/*
		 * 空的叶子页面作为清理的一部分被删除，但最左和
		 * 最右页面永远不会删除。因此，从预9.4
		 * 实例pg_upgrade'd可能包含空的叶子页面，我们需要正确处理
		 * 它们。
		 */
		if (fc_nuncompressed > 0)
		{
			GinPostingList *fc_plist;

			fc_plist = ginCompressPostingList(fc_uncompressed, fc_nuncompressed,
										   BLCKSZ, &fc_npacked);
			fc_totalsize = SizeOfGinPostingList(fc_plist);

			Assert(fc_npacked == fc_nuncompressed);

			memcpy(GinDataLeafPageGetPostingList(fc_page), fc_plist, fc_totalsize);
		}
		else
		{
			fc_totalsize = 0;
		}

		GinDataPageSetDataSize(fc_page, fc_totalsize);
		GinPageSetCompressed(fc_page);
		GinPageGetOpaque(fc_page)->maxoff = InvalidOffsetNumber;
	}

	fc_oldseg = GinDataLeafPageGetPostingList(fc_page);
	fc_writePtr = (Pointer) fc_oldseg;
	fc_segmentend = (Pointer) fc_oldseg + GinDataLeafPageGetPostingListSize(fc_page);
	fc_segno = 0;

	fc_walbuf = ((char *) fc_data) + sizeof(ginxlogRecompressDataLeaf);
	for (fc_actionno = 0; fc_actionno < fc_data->nactions; fc_actionno++)
	{
		uint8		fc_a_segno = *((uint8 *) (fc_walbuf++));
		uint8		fc_a_action = *((uint8 *) (fc_walbuf++));
		GinPostingList *fc_newseg = NULL;
		int			fc_newsegsize = 0;
		ItemPointerData *fc_items = NULL;
		uint16		fc_nitems = 0;
		ItemPointerData *fc_olditems;
		int			fc_nolditems;
		ItemPointerData *fc_newitems;
		int			fc_nnewitems;
		int			fc_segsize;

		/* 从WAL记录中提取我们需要的所有信息 */
		if (fc_a_action == GIN_SEGMENT_INSERT ||
			fc_a_action == GIN_SEGMENT_REPLACE)
		{
			fc_newseg = (GinPostingList *) fc_walbuf;
			fc_newsegsize = SizeOfGinPostingList(fc_newseg);
			fc_walbuf += SHORTALIGN(fc_newsegsize);
		}

		if (fc_a_action == GIN_SEGMENT_ADDITEMS)
		{
			memcpy(&fc_nitems, fc_walbuf, sizeof(uint16));
			fc_walbuf += sizeof(uint16);
			fc_items = (ItemPointerData *) fc_walbuf;
			fc_walbuf += fc_nitems * sizeof(ItemPointerData);
		}

		/* 跳到此操作相关的段 */
		Assert(fc_segno <= fc_a_segno);
		while (fc_segno < fc_a_segno)
		{
			/*
			 * 一旦修改开始并且页面尾部被复制，我们必须
			 * 复制未修改的段。
			 */
			fc_segsize = SizeOfGinPostingList(fc_oldseg);
			if (fc_tailCopy)
			{
				Assert(fc_writePtr + fc_segsize < PageGetSpecialPointer(fc_page));
				memcpy(fc_writePtr, (Pointer) fc_oldseg, fc_segsize);
			}
			fc_writePtr += fc_segsize;
			fc_oldseg = GinNextPostingListSegment(fc_oldseg);
			fc_segno++;
		}

		/*
		 * ADDITEMS操作像REPLACE那样处理，但替换旧段的新段
		 * 是使用从磁盘读取的旧段和来自WAL记录的新项重建的。
		 */
		if (fc_a_action == GIN_SEGMENT_ADDITEMS)
		{
			int			fc_npacked;

			fc_olditems = ginPostingListDecode(fc_oldseg, &fc_nolditems);

			fc_newitems = ginMergeItemPointers(fc_items, fc_nitems,
											fc_olditems, fc_nolditems,
											&fc_nnewitems);
			Assert(fc_nnewitems == fc_nolditems + fc_nitems);

			fc_newseg = ginCompressPostingList(fc_newitems, fc_nnewitems,
											BLCKSZ, &fc_npacked);
			Assert(fc_npacked == fc_nnewitems);

			fc_newsegsize = SizeOfGinPostingList(fc_newseg);
			fc_a_action = GIN_SEGMENT_REPLACE;
		}

		fc_segptr = (Pointer) fc_oldseg;
		if (fc_segptr != fc_segmentend)
			fc_segsize = SizeOfGinPostingList(fc_oldseg);
		else
		{
			/*
			 * 位于最后一个现有段之后。这里只期望INSERT。
			 */
			Assert(fc_a_action == GIN_SEGMENT_INSERT);
			fc_segsize = 0;
		}

		/*
		 * 我们即将开始页面的修改。所以，如果还没有完成的话，复制页面的尾部。
		 */
		if (!fc_tailCopy && fc_segptr != fc_segmentend)
		{
			int			fc_tailSize = fc_segmentend - fc_segptr;

			fc_tailCopy = (Pointer) palloc(fc_tailSize);
			memcpy(fc_tailCopy, fc_segptr, fc_tailSize);
			fc_segptr = fc_tailCopy;
			fc_oldseg = (GinPostingList *) fc_segptr;
			fc_segmentend = fc_segptr + fc_tailSize;
		}

		switch (fc_a_action)
		{
			case GIN_SEGMENT_DELETE:
				fc_segptr += fc_segsize;
				fc_segno++;
				break;

			case GIN_SEGMENT_INSERT:
				/* 在原地复制新段 */
				Assert(fc_writePtr + fc_newsegsize <= PageGetSpecialPointer(fc_page));
				memcpy(fc_writePtr, fc_newseg, fc_newsegsize);
				fc_writePtr += fc_newsegsize;
				break;

			case GIN_SEGMENT_REPLACE:
				/* 在原地复制段的新版本 */
				Assert(fc_writePtr + fc_newsegsize <= PageGetSpecialPointer(fc_page));
				memcpy(fc_writePtr, fc_newseg, fc_newsegsize);
				fc_writePtr += fc_newsegsize;
				fc_segptr += fc_segsize;
				fc_segno++;
				break;

			default:
				elog(ERROR, "unexpected GIN leaf action: %u", fc_a_action);
		}
		fc_oldseg = (GinPostingList *) fc_segptr;
	}

	/* 如果有的话，复制其余未修改的段。 */
	fc_segptr = (Pointer) fc_oldseg;
	if (fc_segptr != fc_segmentend && fc_tailCopy)
	{
		int			fc_restSize = fc_segmentend - fc_segptr;

		Assert(fc_writePtr + fc_restSize <= PageGetSpecialPointer(fc_page));
		memcpy(fc_writePtr, fc_segptr, fc_restSize);
		fc_writePtr += fc_restSize;
	}

	fc_totalsize = fc_writePtr - (Pointer) GinDataLeafPageGetPostingList(fc_page);
	GinDataPageSetDataSize(fc_page, fc_totalsize);
}

static void fc_ginRedoInsertData(Buffer fc_buffer, bool fc_isLeaf, BlockNumber fc_rightblkno, void *fc_rdata)
{
	Page		fc_page = BufferGetPage(fc_buffer);

	if (fc_isLeaf)
	{
		ginxlogRecompressDataLeaf *fc_data = (ginxlogRecompressDataLeaf *) fc_rdata;

		Assert(GinPageIsLeaf(fc_page));

		fc_ginRedoRecompress(fc_page, fc_data);
	}
	else
	{
		ginxlogInsertDataInternal *fc_data = (ginxlogInsertDataInternal *) fc_rdata;
		PostingItem *fc_oldpitem;

		Assert(!GinPageIsLeaf(fc_page));

		/* 在分裂后更新到右页面的链接 */
		fc_oldpitem = GinDataPageGetPostingItem(fc_page, fc_data->offset);
		PostingItemSetBlockNumber(fc_oldpitem, fc_rightblkno);

		GinDataPageAddPostingItem(fc_page, &fc_data->newitem, fc_data->offset);
	}
}

static void fc_ginRedoInsert(XLogReaderState *fc_record)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	ginxlogInsert *fc_data = (ginxlogInsert *) XLogRecGetData(fc_record);
	Buffer		fc_buffer;
#ifdef NOT_USED
	BlockNumber fc_leftChildBlkno = InvalidBlockNumber;
#endif
	BlockNumber fc_rightChildBlkno = InvalidBlockNumber;
	bool		fc_isLeaf = (fc_data->flags & GIN_INSERT_ISLEAF) != 0;

	/*
	 * 首先清除子页面上的不完整分裂标志，如果这完成了一次
	 * 分裂。
	 */
	if (!fc_isLeaf)
	{
		char	   *fc_payload = XLogRecGetData(fc_record) + sizeof(ginxlogInsert);

#ifdef NOT_USED
		fc_leftChildBlkno = BlockIdGetBlockNumber((BlockId) fc_payload);
#endif
		fc_payload += sizeof(BlockIdData);
		fc_rightChildBlkno = BlockIdGetBlockNumber((BlockId) fc_payload);
		fc_payload += sizeof(BlockIdData);

		fc_ginRedoClearIncompleteSplit(fc_record, 1);
	}

	if (XLogReadBufferForRedo(fc_record, 0, &fc_buffer) == BLK_NEEDS_REDO)
	{
		Page		fc_page = BufferGetPage(fc_buffer);
		Size		fc_len;
		char	   *fc_payload = XLogRecGetBlockData(fc_record, 0, &fc_len);

		/* 插入有效负载的方式取决于树类型 */
		if (fc_data->flags & GIN_INSERT_ISDATA)
		{
			Assert(GinPageIsData(fc_page));
			fc_ginRedoInsertData(fc_buffer, fc_isLeaf, fc_rightChildBlkno, fc_payload);
		}
		else
		{
			Assert(!GinPageIsData(fc_page));
			fc_ginRedoInsertEntry(fc_buffer, fc_isLeaf, fc_rightChildBlkno, fc_payload);
		}

		PageSetLSN(fc_page, fc_lsn);
		MarkBufferDirty(fc_buffer);
	}
	if (BufferIsValid(fc_buffer))
		UnlockReleaseBuffer(fc_buffer);
}

static void fc_ginRedoSplit(XLogReaderState *fc_record)
{
	ginxlogSplit *fc_data = (ginxlogSplit *) XLogRecGetData(fc_record);
	Buffer		fc_lbuffer,
				fc_rbuffer,
				fc_rootbuf;
	bool		fc_isLeaf = (fc_data->flags & GIN_INSERT_ISLEAF) != 0;
	bool		fc_isRoot = (fc_data->flags & GIN_SPLIT_ROOT) != 0;

	/*
	 * 首先清除子页面上的不完整分裂标志，如果这完成了一次
	 * 分裂。
	 */
	if (!fc_isLeaf)
		fc_ginRedoClearIncompleteSplit(fc_record, 3);

	if (XLogReadBufferForRedo(fc_record, 0, &fc_lbuffer) != BLK_RESTORED)
		elog(ERROR, "GIN split record did not contain a full-page image of left page");

	if (XLogReadBufferForRedo(fc_record, 1, &fc_rbuffer) != BLK_RESTORED)
		elog(ERROR, "GIN split record did not contain a full-page image of right page");

	if (fc_isRoot)
	{
		if (XLogReadBufferForRedo(fc_record, 2, &fc_rootbuf) != BLK_RESTORED)
			elog(ERROR, "GIN split record did not contain a full-page image of root page");
		UnlockReleaseBuffer(fc_rootbuf);
	}

	UnlockReleaseBuffer(fc_rbuffer);
	UnlockReleaseBuffer(fc_lbuffer);
}

/*
 * VACUUM_PAGE记录仅包含页面的完整映像，类似于
 * XLOG_FPI记录。
 */
static void fc_ginRedoVacuumPage(XLogReaderState *fc_record)
{
	Buffer		fc_buffer;

	if (XLogReadBufferForRedo(fc_record, 0, &fc_buffer) != BLK_RESTORED)
	{
		elog(ERROR, "replay of gin entry tree page vacuum did not restore the page");
	}
	UnlockReleaseBuffer(fc_buffer);
}

static void fc_ginRedoVacuumDataLeafPage(XLogReaderState *fc_record)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	Buffer		fc_buffer;

	if (XLogReadBufferForRedo(fc_record, 0, &fc_buffer) == BLK_NEEDS_REDO)
	{
		Page		fc_page = BufferGetPage(fc_buffer);
		Size		fc_len;
		ginxlogVacuumDataLeafPage *fc_xlrec;

		fc_xlrec = (ginxlogVacuumDataLeafPage *) XLogRecGetBlockData(fc_record, 0, &fc_len);

		Assert(GinPageIsLeaf(fc_page));
		Assert(GinPageIsData(fc_page));

		fc_ginRedoRecompress(fc_page, &fc_xlrec->data);
		PageSetLSN(fc_page, fc_lsn);
		MarkBufferDirty(fc_buffer);
	}
	if (BufferIsValid(fc_buffer))
		UnlockReleaseBuffer(fc_buffer);
}

static void fc_ginRedoDeletePage(XLogReaderState *fc_record)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	ginxlogDeletePage *fc_data = (ginxlogDeletePage *) XLogRecGetData(fc_record);
	Buffer		fc_dbuffer;
	Buffer		fc_pbuffer;
	Buffer		fc_lbuffer;
	Page		fc_page;

	/*
	 * 首先锁定左页面，以防止与
	 * ginStepRight()可能发生的死锁。
	 */
	if (XLogReadBufferForRedo(fc_record, 2, &fc_lbuffer) == BLK_NEEDS_REDO)
	{
		fc_page = BufferGetPage(fc_lbuffer);
		Assert(GinPageIsData(fc_page));
		GinPageGetOpaque(fc_page)->rightlink = fc_data->rightLink;
		PageSetLSN(fc_page, fc_lsn);
		MarkBufferDirty(fc_lbuffer);
	}

	if (XLogReadBufferForRedo(fc_record, 0, &fc_dbuffer) == BLK_NEEDS_REDO)
	{
		fc_page = BufferGetPage(fc_dbuffer);
		Assert(GinPageIsData(fc_page));
		GinPageSetDeleted(fc_page);
		GinPageSetDeleteXid(fc_page, fc_data->deleteXid);
		PageSetLSN(fc_page, fc_lsn);
		MarkBufferDirty(fc_dbuffer);
	}

	if (XLogReadBufferForRedo(fc_record, 1, &fc_pbuffer) == BLK_NEEDS_REDO)
	{
		fc_page = BufferGetPage(fc_pbuffer);
		Assert(GinPageIsData(fc_page));
		Assert(!GinPageIsLeaf(fc_page));
		GinPageDeletePostingItem(fc_page, fc_data->parentOffset);
		PageSetLSN(fc_page, fc_lsn);
		MarkBufferDirty(fc_pbuffer);
	}

	if (BufferIsValid(fc_lbuffer))
		UnlockReleaseBuffer(fc_lbuffer);
	if (BufferIsValid(fc_pbuffer))
		UnlockReleaseBuffer(fc_pbuffer);
	if (BufferIsValid(fc_dbuffer))
		UnlockReleaseBuffer(fc_dbuffer);
}

static void fc_ginRedoUpdateMetapage(XLogReaderState *fc_record)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	ginxlogUpdateMeta *fc_data = (ginxlogUpdateMeta *) XLogRecGetData(fc_record);
	Buffer		fc_metabuffer;
	Page		fc_metapage;
	Buffer		fc_buffer;

	/*
	 * 恢复元页面。这本质上与完整页面
	 * 图像相同，因此不看
	 * LSN，无条件恢复元页面，以避免撕裂页面的危险。
	 */
	fc_metabuffer = XLogInitBufferForRedo(fc_record, 0);
	Assert(BufferGetBlockNumber(fc_metabuffer) == GIN_METAPAGE_BLKNO);
	fc_metapage = BufferGetPage(fc_metabuffer);

	GinInitMetabuffer(fc_metabuffer);
	memcpy(GinPageGetMeta(fc_metapage), &fc_data->metadata, sizeof(GinMetaPageData));
	PageSetLSN(fc_metapage, fc_lsn);
	MarkBufferDirty(fc_metabuffer);

	if (fc_data->ntuples > 0)
	{
		/*
		 * 插入到尾页面
		 */
		if (XLogReadBufferForRedo(fc_record, 1, &fc_buffer) == BLK_NEEDS_REDO)
		{
			Page		fc_page = BufferGetPage(fc_buffer);
			OffsetNumber fc_off;
			int			fc_i;
			Size		fc_tupsize;
			char	   *fc_payload;
			IndexTuple	fc_tuples;
			Size		fc_totaltupsize;

			fc_payload = XLogRecGetBlockData(fc_record, 1, &fc_totaltupsize);
			fc_tuples = (IndexTuple) fc_payload;

			if (PageIsEmpty(fc_page))
				fc_off = FirstOffsetNumber;
			else
				fc_off = OffsetNumberNext(PageGetMaxOffsetNumber(fc_page));

			for (fc_i = 0; fc_i < fc_data->ntuples; fc_i++)
			{
				fc_tupsize = IndexTupleSize(fc_tuples);

				if (PageAddItem(fc_page, (Item) fc_tuples, fc_tupsize, fc_off,
								false, false) == InvalidOffsetNumber)
					elog(ERROR, "failed to add item to index page");

				fc_tuples = (IndexTuple) (((char *) fc_tuples) + fc_tupsize);

				fc_off++;
			}
			Assert(fc_payload + fc_totaltupsize == (char *) fc_tuples);

			/*
			 * 增加堆元组计数器
			 */
			GinPageGetOpaque(fc_page)->maxoff++;

			PageSetLSN(fc_page, fc_lsn);
			MarkBufferDirty(fc_buffer);
		}
		if (BufferIsValid(fc_buffer))
			UnlockReleaseBuffer(fc_buffer);
	}
	else if (fc_data->prevTail != InvalidBlockNumber)
	{
		/*
		 * 新尾部
		 */
		if (XLogReadBufferForRedo(fc_record, 1, &fc_buffer) == BLK_NEEDS_REDO)
		{
			Page		fc_page = BufferGetPage(fc_buffer);

			GinPageGetOpaque(fc_page)->rightlink = fc_data->newRightlink;

			PageSetLSN(fc_page, fc_lsn);
			MarkBufferDirty(fc_buffer);
		}
		if (BufferIsValid(fc_buffer))
			UnlockReleaseBuffer(fc_buffer);
	}

	UnlockReleaseBuffer(fc_metabuffer);
}

static void fc_ginRedoInsertListPage(XLogReaderState *fc_record)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	ginxlogInsertListPage *fc_data = (ginxlogInsertListPage *) XLogRecGetData(fc_record);
	Buffer		fc_buffer;
	Page		fc_page;
	OffsetNumber fc_l,
				fc_off = FirstOffsetNumber;
	int			fc_i,
				fc_tupsize;
	char	   *fc_payload;
	IndexTuple	fc_tuples;
	Size		fc_totaltupsize;

	/* 我们总是重新初始化页面。 */
	fc_buffer = XLogInitBufferForRedo(fc_record, 0);
	fc_page = BufferGetPage(fc_buffer);

	GinInitBuffer(fc_buffer, GIN_LIST);
	GinPageGetOpaque(fc_page)->rightlink = fc_data->rightlink;
	if (fc_data->rightlink == InvalidBlockNumber)
	{
		/* 子列表的尾部 */
		GinPageSetFullRow(fc_page);
		GinPageGetOpaque(fc_page)->maxoff = 1;
	}
	else
	{
		GinPageGetOpaque(fc_page)->maxoff = 0;
	}

	fc_payload = XLogRecGetBlockData(fc_record, 0, &fc_totaltupsize);

	fc_tuples = (IndexTuple) fc_payload;
	for (fc_i = 0; fc_i < fc_data->ntuples; fc_i++)
	{
		fc_tupsize = IndexTupleSize(fc_tuples);

		fc_l = PageAddItem(fc_page, (Item) fc_tuples, fc_tupsize, fc_off, false, false);

		if (fc_l == InvalidOffsetNumber)
			elog(ERROR, "failed to add item to index page");

		fc_tuples = (IndexTuple) (((char *) fc_tuples) + fc_tupsize);
		fc_off++;
	}
	Assert((char *) fc_tuples == fc_payload + fc_totaltupsize);

	PageSetLSN(fc_page, fc_lsn);
	MarkBufferDirty(fc_buffer);

	UnlockReleaseBuffer(fc_buffer);
}

static void fc_ginRedoDeleteListPages(XLogReaderState *fc_record)
{
	XLogRecPtr	fc_lsn = fc_record->EndRecPtr;
	ginxlogDeleteListPages *fc_data = (ginxlogDeleteListPages *) XLogRecGetData(fc_record);
	Buffer		fc_metabuffer;
	Page		fc_metapage;
	int			fc_i;

	fc_metabuffer = XLogInitBufferForRedo(fc_record, 0);
	Assert(BufferGetBlockNumber(fc_metabuffer) == GIN_METAPAGE_BLKNO);
	fc_metapage = BufferGetPage(fc_metabuffer);

	GinInitMetabuffer(fc_metabuffer);

	memcpy(GinPageGetMeta(fc_metapage), &fc_data->metadata, sizeof(GinMetaPageData));
	PageSetLSN(fc_metapage, fc_lsn);
	MarkBufferDirty(fc_metabuffer);

	/*
	 * 在正常操作中，shiftList() 同时对所有待删除的页面加独占锁。然而，在重放期间，可以逐个加锁。这取决于我们从列表的头部删除页面这一事实，以及读取者在释放他们所处的页面之前会对下一个页面加共享锁。因此，我们无法越过一个正在使用或即将访问我们要删除的任何页面的读取者。新的读取者将在我们的元页面锁后面阻塞，然后看到一个完全更新的页面列表。
	 *
	 * 已删除的页面不会被拍摄完整页图像。相反，它们被重新初始化为空的已删除页面。它们的右链接不需要被保留，因为如上所述，没有新的读取者可以看到这些页面。
	 */
	for (fc_i = 0; fc_i < fc_data->ndeleted; fc_i++)
	{
		Buffer		fc_buffer;
		Page		fc_page;

		fc_buffer = XLogInitBufferForRedo(fc_record, fc_i + 1);
		fc_page = BufferGetPage(fc_buffer);
		GinInitBuffer(fc_buffer, GIN_DELETED);

		PageSetLSN(fc_page, fc_lsn);
		MarkBufferDirty(fc_buffer);

		UnlockReleaseBuffer(fc_buffer);
	}
	UnlockReleaseBuffer(fc_metabuffer);
}

void gin_redo(XLogReaderState *fc_record)
{
	uint8		fc_info = XLogRecGetInfo(fc_record) & ~XLR_INFO_MASK;
	MemoryContext fc_oldCtx;

	/*
	 * GIN 索引不需要任何冲突处理。注意：如果我们在 B-tree 中实施类似的优化，并在 VACUUM 之外移除已删除的元组，我们需要在这里处理这个问题。
	 */

	fc_oldCtx = MemoryContextSwitchTo(opCtx);
	switch (fc_info)
	{
		case XLOG_GIN_CREATE_PTREE:
			fc_ginRedoCreatePTree(fc_record);
			break;
		case XLOG_GIN_INSERT:
			fc_ginRedoInsert(fc_record);
			break;
		case XLOG_GIN_SPLIT:
			fc_ginRedoSplit(fc_record);
			break;
		case XLOG_GIN_VACUUM_PAGE:
			fc_ginRedoVacuumPage(fc_record);
			break;
		case XLOG_GIN_VACUUM_DATA_LEAF_PAGE:
			fc_ginRedoVacuumDataLeafPage(fc_record);
			break;
		case XLOG_GIN_DELETE_PAGE:
			fc_ginRedoDeletePage(fc_record);
			break;
		case XLOG_GIN_UPDATE_META_PAGE:
			fc_ginRedoUpdateMetapage(fc_record);
			break;
		case XLOG_GIN_INSERT_LISTPAGE:
			fc_ginRedoInsertListPage(fc_record);
			break;
		case XLOG_GIN_DELETE_LISTPAGE:
			fc_ginRedoDeleteListPages(fc_record);
			break;
		default:
			elog(PANIC, "gin_redo: unknown op code %u", fc_info);
	}
	MemoryContextSwitchTo(fc_oldCtx);
	MemoryContextReset(opCtx);
}

void gin_xlog_startup(void)
{
	opCtx = AllocSetContextCreate(CurrentMemoryContext,
								  "GIN recovery temporary context",
								  ALLOCSET_DEFAULT_SIZES);
}

void gin_xlog_cleanup(void)
{
	MemoryContextDelete(opCtx);
	opCtx = NULL;
}

/*
 * 在对 GIN 页面运行一致性检查之前对其进行掩码处理。
 */
void gin_mask(char *fc_pagedata, BlockNumber fc_blkno)
{
	Page		fc_page = (Page) fc_pagedata;
	PageHeader	fc_pagehdr = (PageHeader) fc_page;
	GinPageOpaque fc_opaque;

	mask_page_lsn_and_checksum(fc_page);
	fc_opaque = GinPageGetOpaque(fc_page);

	mask_page_hint_bits(fc_page);

	/*
	 * 对于一个 GIN_DELETED 页面，该页面被初始化为空。因此，对整个页面内容进行掩码处理。对于其他页面，如果 pd_lower 似乎被正确设置，则对整个页面进行掩码处理。
	 */
	if (fc_opaque->flags & GIN_DELETED)
		mask_page_content(fc_page);
	else if (fc_pagehdr->pd_lower > SizeOfPageHeaderData)
		mask_unused_space(fc_page);
}
